package com.googlecode.lookie.swing.iconlist;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JList;
import javax.swing.SwingUtilities;

public abstract class DelayedLabelProvider
    implements LabelProvider
{

    private ExecutorService executorService = Executors.newFixedThreadPool( 2 );

    private Map<Object, Object> _lockObjectList = new HashMap<Object, Object>();

    private List<Object> _loadingMap = new ArrayList<Object>();

    private final JList list;

    private boolean cancelled;

    public DelayedLabelProvider( JList list )
    {
        this.list = list;
    }

    public Icon getIcon( Object value, int size )
    {
        synchronized ( getLockObject( value ) )
        {
            if ( isImageReady( value ) )
            {
                BufferedImage image = getImage( value );
                // TODO possibly corrupt file -_> image = null
                if ( image != null )
                {
                    if ( image.getWidth() > size || image.getHeight() > size )
                    {
                        image = scaleIcon( image, size );
                    }
                    return new BufferedImageIcon( image );
                }
            }
            else
            {
                prepare( value );
            }
        }
        return null;
    }

    private void prepare( final Object value )
    {
        if ( !_loadingMap.contains( value ) )
        {
            _loadingMap.add( value );
            Runnable runnable = new Runnable()
            {

                public void run()
                {
                    if ( !cancelled )
                    {
                        prepareImage( value );
                        SwingUtilities.invokeLater( new Runnable()
                        {

                            public void run()
                            {

                                Object lockObject = getLockObject( value );
                                synchronized ( lockObject )
                                {
                                    if ( !cancelled )
                                    {
                                        if ( list.getModel() instanceof DefaultListModel )
                                        {

                                            int index = ( (DefaultListModel) list.getModel() ).indexOf( value );
                                            if ( index >= 0 )
                                            {
                                                Rectangle r = list.getCellBounds( index, index );
                                                list.repaint( r );
                                            }
                                        }
                                        else
                                        {
                                            // TODO: should not always repaint
                                            list.repaint();
                                        }
                                        _loadingMap.remove( lockObject );
                                        _lockObjectList.remove( value );
                                    }
                                }
                            }

                        } );
                    }
                }

            };
            executorService.execute( runnable );
        }
    }

    public void cancel()
    {

        cancelled = true;
        executorService.shutdown();
        try
        {
            executorService.awaitTermination( Long.MAX_VALUE, TimeUnit.SECONDS );
        }
        catch ( InterruptedException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        _lockObjectList.clear();
        _loadingMap.clear();

        cancelled = false;
        executorService = Executors.newFixedThreadPool( 2 );
    }

    private Object getLockObject( Object value )
    {
        Object lockObject = _lockObjectList.get( value );
        if ( lockObject == null )
        {
            synchronized ( _lockObjectList )
            {
                lockObject = _lockObjectList.get( value );
                if ( lockObject == null )
                {
                    lockObject = new Object();
                    _lockObjectList.put( value, lockObject );
                }
            }
        }
        return lockObject;
    }

    public static BufferedImage scaleIcon( BufferedImage inImage, int size )
    {
        final double scale = Math.min( (double) size / (double) inImage.getHeight( null ), (double) size
            / (double) inImage.getWidth( null ) );

        // Determine scaled size of new image.
        int scaledH, scaledW;
        if ( ( scaledW = (int) ( scale * inImage.getWidth( null ) ) ) <= 0 )
        {
            scaledW = 1;
        }
        if ( ( scaledH = (int) ( scale * inImage.getHeight( null ) ) ) <= 0 )
        {
            scaledH = 1;
        }

        final BufferedImage outImage = new BufferedImage( scaledW, scaledH, BufferedImage.TYPE_INT_RGB );
        final Graphics2D g2d;
        g2d = outImage.createGraphics();
        g2d.setRenderingHint( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED );
        g2d.drawImage( inImage, AffineTransform.getScaleInstance( scale, scale ), null );
        g2d.dispose();

        return outImage;
    }

    protected abstract boolean isImageReady( Object value );

    protected abstract BufferedImage getImage( Object value );

    protected abstract void prepareImage( Object value );

}
